/*
 *
 *  * Copyright (C) 2015 Square, Inc.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */
package com.lark.oapi.okio;

import javax.annotation.Nullable;

/**
 * A segment of a buffer.
 *
 * <p>Each segment in a buffer is a circularly-linked list node referencing the following and
 * preceding segments in the buffer.
 *
 * <p>Each segment in the pool is a singly-linked list node referencing the rest of segments in the
 * pool.
 *
 * <p>The underlying byte arrays of segments may be shared between buffers and byte strings. When a
 * segment's byte array is shared the segment may not be recycled, nor may its byte data be changed.
 * The lone exception is that the owner segment is allowed to append to the segment, writing data at
 * {@code limit} and beyond. There is a single owning segment for each byte array. Positions,
 * limits, prev, and next references are not shared.
 */
final class Segment {

    /**
     * The size of all segments in bytes.
     */
    static final int SIZE = 8192;

    /**
     * Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes.
     */
    static final int SHARE_MINIMUM = 1024;

    final byte[] data;

    /**
     * The next byte of application data byte to read in this segment.
     */
    int pos;

    /**
     * The first byte of available data ready to be written to.
     */
    int limit;

    /**
     * True if other segments or byte strings use the same byte array.
     */
    boolean shared;

    /**
     * True if this segment owns the byte array and can append to it, extending {@code limit}.
     */
    boolean owner;

    /**
     * Next segment in a linked or circularly-linked list.
     */
    Segment next;

    /**
     * Previous segment in a circularly-linked list.
     */
    Segment prev;

    Segment() {
        this.data = new byte[SIZE];
        this.owner = true;
        this.shared = false;
    }

    Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) {
        this.data = data;
        this.pos = pos;
        this.limit = limit;
        this.shared = shared;
        this.owner = owner;
    }

    /**
     * Returns a new segment that shares the underlying byte array with this. Adjusting pos and limit
     * are safe but writes are forbidden. This also marks the current segment as shared, which
     * prevents it from being pooled.
     */
    final Segment sharedCopy() {
        shared = true;
        return new Segment(data, pos, limit, true, false);
    }

    /**
     * Returns a new segment that its own private copy of the underlying byte array.
     */
    final Segment unsharedCopy() {
        return new Segment(data.clone(), pos, limit, false, true);
    }

    /**
     * Removes this segment of a circularly-linked list and returns its successor. Returns null if the
     * list is now empty.
     */
    public final @Nullable
    Segment pop() {
        Segment result = next != this ? next : null;
        prev.next = next;
        next.prev = prev;
        next = null;
        prev = null;
        return result;
    }

    /**
     * Appends {@code segment} after this segment in the circularly-linked list. Returns the pushed
     * segment.
     */
    public final Segment push(Segment segment) {
        segment.prev = this;
        segment.next = next;
        next.prev = segment;
        next = segment;
        return segment;
    }

    /**
     * Splits this head of a circularly-linked list into two segments. The first segment contains the
     * data in {@code [pos..pos+byteCount)}. The second segment contains the data in {@code
     * [pos+byteCount..limit)}. This can be useful when moving partial segments from one buffer to
     * another.
     *
     * <p>Returns the new head of the circularly-linked list.
     */
    public final Segment split(int byteCount) {
        if (byteCount <= 0 || byteCount > limit - pos) {
            throw new IllegalArgumentException();
        }
        Segment prefix;

        // We have two competing performance goals:
        //  - Avoid copying data. We accomplish this by sharing segments.
        //  - Avoid short shared segments. These are bad for performance because they are readonly and
        //    may lead to long chains of short segments.
        // To balance these goals we only share segments when the copy will be large.
        if (byteCount >= SHARE_MINIMUM) {
            prefix = sharedCopy();
        } else {
            prefix = SegmentPool.take();
            System.arraycopy(data, pos, prefix.data, 0, byteCount);
        }

        prefix.limit = prefix.pos + byteCount;
        pos += byteCount;
        prev.push(prefix);
        return prefix;
    }

    /**
     * Call this when the tail and its predecessor may both be less than half full. This will copy
     * data so that segments can be recycled.
     */
    public final void compact() {
        if (prev == this) {
            throw new IllegalStateException();
        }
        if (!prev.owner) {
            return; // Cannot compact: prev isn't writable.
        }
        int byteCount = limit - pos;
        int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
        if (byteCount > availableByteCount) {
            return; // Cannot compact: not enough writable space.
        }
        writeTo(prev, byteCount);
        pop();
        SegmentPool.recycle(this);
    }

    /**
     * Moves {@code byteCount} bytes from this segment to {@code sink}.
     */
    public final void writeTo(Segment sink, int byteCount) {
        if (!sink.owner) {
            throw new IllegalArgumentException();
        }
        if (sink.limit + byteCount > SIZE) {
            // We can't fit byteCount bytes at the sink's current position. Shift sink first.
            if (sink.shared) {
                throw new IllegalArgumentException();
            }
            if (sink.limit + byteCount - sink.pos > SIZE) {
                throw new IllegalArgumentException();
            }
            System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
            sink.limit -= sink.pos;
            sink.pos = 0;
        }

        System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
        sink.limit += byteCount;
        pos += byteCount;
    }
}
